#!/usr/bin/env python
# -*- coding: utf-8 -*-
# http://docs.python.org/whatsnew/pep-328.html
from __future__ import absolute_import
from __future__ import print_function   # deletes the print statement
from __future__ import unicode_literals # unicode by default

# Author:  Nando Florestan -- http://oui.com.br
# License: LGPL


from datetime import datetime
from sqlalchemy import Table, Column, String, DateTime, Integer, \
                       ForeignKey, Unicode, BOOLEAN, desc
from sqlalchemy.orm import relation, backref, deferred, synonym
from sqlalchemy.orm.exc import NoResultFound
from .. import ShowUserThisException
from ..email_validator import EmailValidator
from ..wp_model import metadata, mapper, session, \
                       BaseModel, prop_strip_require, \
                       prop_strip_capitalize



def login_is_email():
    """In some applications the login is a simple username; in others an
    email address is required. This option is configurable in this model.
    Required fields change according to this option.
    This function reads that option and returns True or False.
    """
    from cherrypy import request
    app = request.app
    if app is None:  return True
    return app.config["/"]["login_is_email"]

groups_table = Table('group', metadata,
    Column('id', Integer, primary_key=True),
    Column('name', Unicode(16), unique=True, nullable=False),
    Column('display_name', Unicode(255), nullable=False),
    Column('created', DateTime, default=datetime.now, nullable=False)
)

users_table = Table('user', metadata,
    Column('id', Integer, primary_key=True),
    Column('name', Unicode(255), unique=True),
    Column('email', Unicode(255), unique=True, nullable=False),
    Column('display_name', Unicode(160), nullable=False),
    Column('password', Unicode(40), nullable=False),
    Column('created' , DateTime, default=datetime.utcnow, nullable=False),
    Column('changed' , DateTime, default=datetime.utcnow, nullable=False),
    Column("profession", Unicode(160), default=''),
    Column("organization", Unicode(90), default=''),
    Column("phones", Unicode(160), default=''), # one per line
    Column("avatar", Unicode(40), default=''),
    Column("newsletter", BOOLEAN, default=False),
    # Address:
    Column('street',  Unicode(160), default=''),
    Column("district", Unicode(80), default=''),
    Column('city'    , Unicode(80), default=''),
    Column('province', Unicode(40), default=''),
    Column('country' , Unicode(40), default=''),
    Column('zipcode' , Unicode(16), default='')
)

permissions_table = Table('permission', metadata,
    Column('id', Integer, primary_key=True),
    Column('name', Unicode(16), unique=True, nullable=False),
    Column('description', Unicode(255), default='')
)

user_group_table = Table('user_group', metadata,
    Column('user_id', Integer, ForeignKey(users_table.c.id)),
    Column('group_id', Integer, ForeignKey(groups_table.c.id))
)

group_permission_table = Table('group_permission', metadata,
    Column('group_id', Integer, ForeignKey(groups_table.c.id)),
    Column('permission_id', Integer, ForeignKey(permissions_table.c.id))
)


class Group(BaseModel):
    def __init__(self, name, display_name):
        self.name = name
        self.display_name = display_name
    
    @staticmethod
    def byname(name):
        """Returns the group with the specified name, or None."""
        return session.query(Group).filter(Group.name==name).one()
    
    @staticmethod
    def get_or_create(name, display_name):
        """If the specified Group already exists in the database, returns it.
        Else, creates it and returns it.
        """
        g = Group.byname(name)
        if g is None:
            g = Group(name=name, display_name=display_name)
        return g
    
    def __repr__(self):
        return str(self.id) + ":" + self.name



email_validator = EmailValidator(fix=True)

class User(BaseModel):
    @property
    def details_as_text(self):
        a = ['User #{0.id}: {0.name}']
        a.append('Nome: {0.display_name}')
        a.append('E-mail: {0.email}')
        if self.profession:    a.append('Profissão: {0.profession}')
        if self.organization:  a.append('Organização: {0.organization}')
        a.append('Telefones: {0.phones}')
        a.append('Endereço: {0.street}\n{0.district}\n{0.city} ― ' \
                 '{0.province}\n{0.country}\n{0.zipcode}')
        return ('\n'.join(a)).format(self)
        
    
    def permissions(self):
        perms = set()
        for g in self.groups:  perms = perms | set(g.permissions)
        return perms
    permissions = property(permissions)
    
    def name():
        def fget(self):  return self._name
        def fset(self, v): # if username is configured to be email,
            if login_is_email():
                self.email = v # just use the email setter
            else:
                if not v:
                    raise ShowUserThisException(_( \
                        'O login não pode ficar em branco.'))
                v = v.strip()
                if v == "":
                    raise ShowUserThisException(_(\
                        'O login não pode ficar em branco.'))
                self._name = v.lower()
        return locals()
    name = property(**name())
    
    def email():
        def fget(self):  return self._email
        def fset(self, val):
            email, error_msg = email_validator.validate(val)
            if error_msg:
                raise ShowUserThisException(error_msg)
            self._email = email
            if login_is_email():
                self._name = email
        return locals()
    email = property(**email())
    
    def login():
        def fget(self):
            if login_is_email():
                return self.email
            else:
                return self.name
        def fset(self, val):
            if login_is_email():
                self.email = val
            else:
                self.name = val
        return locals()
    login = property(**login())
    
    def password():
        def fget(self):  return self._password
        def fset(self, val):
            if val == "":
                raise ShowUserThisException(_( \
                    'A senha não pode ficar em branco.'))
            if len(val) < 6:
                raise ShowUserThisException(_( \
                    'A senha está curta demais.'))
            self._password = val
        return locals()
    password = property(**password())
    
    display_name = prop_strip_capitalize("_display_name")
    street  = prop_strip_capitalize("_street")
    district = prop_strip_capitalize("_district")
    city     = prop_strip_capitalize("_city")
    province = prop_strip_capitalize("_province")
    country = prop_strip_capitalize("_country")

    @staticmethod
    def new_empty():
        # Trick to bypass required fields
        o = User(email='juhbgeuhb@jbcufichbjufbh.com.br',
                 display_name="John Cleese", password="password")
        o._name = ''
        o._email = ''
        o._display_name = ''
        o._password = ''
        return o

    def __init__(self, **k):
        """Example: u = User(email=u'email@dominio.com.br', name="john",
                             display_name=u"John Smith", password=u"password")
        """
        # Mandatory fields
        self.email = k.pop("email")
        self.display_name = k.pop("display_name")
        self.password    = k.pop("password")
        # Some defaults are provided here. TODO: Make configurable
        self.newsletter = k.pop("newsletter", True)
        self.city       = k.pop("city"      , 'São Paulo')
        self.province   = k.pop("province"  , 'SP')
        self.country    = k.pop("country"   , 'Brasil')
        # Call set for the rest of the fields
        self.set(**k) # this also handles mandatory field "name"
    
    @staticmethod
    def new(**k):
        """Validates unique constraints before inserting,
        in order to issue friendly error messages.
        """
        l = User.count(User._email == k["email"])
        if l > 0:
            raise ShowUserThisException('Esse e-mail já está cadastrado. ' \
                               'Faça login ao invés de um novo registro.')
        if not login_is_email():
            l = User.count(User.name==k["name"])
            #l = User.count_by(name=k["name"])
            #l = User.count("name = '%s'" % (k["name"])) # SQLAlchemy error if unicode, fixed in 2764
            #l = User.count(str("name = '%s'" % (k["name"])))
            if l > 0:
                raise ShowUserThisException('Esse login já existe. ' \
                                   'Por favor escolha outro.')
        return User(**k)

    def set(self, **k):
        super(User, self).set(**k)
        if not login_is_email():
            self.name = User.getlogin(**k)
    
    @staticmethod
    def get_by_login(login):
        if login_is_email():
            return session.query(User).filter(User._email==login).one()
        else:
            return session.query(User).filter(User._name==login).one()
    
    @staticmethod
    def getlogin(**k):
        a = k.get("login", None)
        b = k.get("name", None)
        if a == None: return b
        else:         return a

    def __repr__(self):
        return "".join([
            unicode(self.id),
            ':"',
            unicode(self.name),
            '" <',
            unicode(self.email),
            '>'
        ])

    def __str__(self):
        return self.login
    
    @staticmethod
    def havingpermission(perm):
        """Returns all the users who have the specified permission.
        The parameter may be a Permission instance, a permission name,
        or a permission id.
        """
        if   isinstance(perm, Permission):
            perm = perm.permission_id
        elif isinstance(perm, basestring):
            perm = Permission.byname(perm).id
        return session.query(User) \
            .filter(User.id==user_group_table.c.user_id) \
            .filter(user_group_table.c.group_id ==               \
                    group_permission_table.c.group_id)            \
            .filter(group_permission_table.c.permission_id==perm)  \
            .all()



class Permission(BaseModel):
    def __init__(self, name, description=u""):
        self.name = name
        self.description = description
    
    def name():
        def fget(self):  return self._name
        def fset(self, val):
            v = val.strip()
            if v == "":
                raise ShowUserThisException( \
                    'O nome não pode ficar em branco.')
            self._name = v
        return locals()
    name = property(**name())
    
    def __repr__(self):
        return str(self.id) + ":" + self.name
    
    def __str__(self):
        return self.name
    
    @staticmethod
    def byname(name):
        """Returns the permission with the specified name, or None."""
        return session.query(Permission).filter(Permission.name==name).one()
    
    @staticmethod
    def get_or_create(name, descr):
        """If the specified permission already exists in the database,
        returns it. Else, creates it and returns it.
        Parameters: name and description.
        """
        p = Permission.byname(name)
        if p is None:
            p = Permission(name=name, description=descr)
        return p




"""
db.mapper(Visit, visits_table)
db.mapper(VisitIdentity, visit_identity_table,
      properties=dict(users=relation(User, backref='visit_identity')))
"""
mapper(User, users_table,
    properties = {
        "_name" : users_table.c.name,
        "_email" : users_table.c.email,
        '_display_name'  : users_table.c.display_name,
        "_password"      : users_table.c.password,
        "_street"   : users_table.c.street,
        "_district" : users_table.c.district,
        "_city"     : users_table.c.city,
        "_province" : users_table.c.province,
        "_country"  : users_table.c.country
    }
)
mapper(Group, groups_table,
      properties=dict(users=relation(User, secondary=user_group_table,
                                     backref='groups')))
mapper(Permission, permissions_table,
      properties = {
            "groups":relation(Group, secondary=group_permission_table,
                              backref='permissions'),
            "name":synonym("_name", map_column=True),
      })



auth_tables = [groups_table, users_table, permissions_table,
               user_group_table, group_permission_table]

def drop(app):
    if raw_input("Drop auth tables LOSING DATA? ") != "y": return
    print("Dropping the tables!")
    metadata.drop_all(bind=session.bind, tables=auth_tables)

def init(app, email='god@god.org', password='godgodgod', user='god',
         display='God'):
    """Initializes the database and creates the first user, god, with the
    password "obsolete" and the e-mail god@god.org.
    This data should then be changed when first running the application.
    """
    print("Creating auth tables.")
    metadata.create_all(bind=session.bind, tables=auth_tables)
    print("Creating permissions, groups and first user.")
    #session.configure(bind=app.sa.engine)
    p1 = Permission(name='PostEdit',
                    description='Create and edit posts')
    p2 = Permission(name='PostRemove', description='Remove posts')
    p3 = Permission(name='Master',
                    description='Create and delete catalogues and blogs')
    p4 = Permission(name='CatEdit',
                    description='Create, edit and remove categories and pieces')
    p5 = Permission(name='Admin', description='Manage users')
    allPerms = (p1,p2,p3,p4,p5)
    session.add_all(allPerms)
    admin  = Group(name='admins' , display_name='Administrators')
    editor = Group(name='editors', display_name='Editors')
    session.add_all((admin, editor))
    editor.permissions = [p1,p2,p4]
    admin .permissions.extend(allPerms)
    session.commit()
    try:
        god = session.query(User).filter(User.id == 1).one()
    except NoResultFound, e:
        if login_is_email():
            god = User(email=email, password=password, display_name=display)
        else:
            god = User(name=user, email=email, password=password, \
                       display_name=display)
    admin .users.append(god)
    editor.users.append(god)
    session.commit()


def reset(app):
    drop(app)
    init(app)
